Skip to content

Fix streaming tool calls when reasoning parser is active#93

Closed
janhilgard wants to merge 1 commit intowaybarrios:mainfrom
janhilgard:fix/streaming-tool-calls-with-reasoning
Closed

Fix streaming tool calls when reasoning parser is active#93
janhilgard wants to merge 1 commit intowaybarrios:mainfrom
janhilgard:fix/streaming-tool-calls-with-reasoning

Conversation

@janhilgard
Copy link
Copy Markdown
Collaborator

Summary

  • Fix streaming tool calls being emitted as raw text when --reasoning-parser is enabled
  • The reasoning parser branch in stream_chat_completion() was skipping tool call parsing entirely, causing <tool_call> markup to leak into content instead of being parsed into structured tool_calls objects
  • Adds tool call parsing to the reasoning branch, mirroring the existing logic in the standard path

Problem

When both --reasoning-parser deepseek_r1 and --tool-call-parser hermes are enabled, streaming responses emit raw tool call markup as plain text:

{"delta": {"content": "<tool_call>\n<function=get_weather>\n<parameter=location>\nPrague\n</parameter>\n</function>\n</tool_call>", "tool_calls": null}, "finish_reason": "stop"}

Fix

After this change, tool calls are correctly parsed and emitted:

{"delta": {"tool_calls": [{"index": 0, "id": "call_4b99a63b", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\": \"Prague\"}"}}]}, "finish_reason": "tool_calls"}

Test plan

  • Streaming tool call with --reasoning-parser deepseek_r1 --tool-call-parser hermes returns structured tool_calls
  • finish_reason is "tool_calls" (not "stop")
  • Reasoning-only responses (no tool calls) still work correctly
  • Non-streaming tool calls unaffected
  • black formatting passes

🤖 Generated with Claude Code

The reasoning parser branch in stream_chat_completion() was missing
tool call parsing entirely. When both --reasoning-parser and
--tool-call-parser were enabled, streaming responses would emit raw
tool call markup (e.g. <tool_call><function=...>) as plain content
text instead of structured tool_calls objects.

This adds tool call parsing to the reasoning parser branch, mirroring
the existing logic in the standard (non-reasoning) path. Tool calls
are now correctly detected, buffered during accumulation, and emitted
as structured tool_calls with finish_reason="tool_calls".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Thump604
Copy link
Copy Markdown
Collaborator

Thump604 commented Apr 8, 2026

@waybarrios, @janhilgard: status note plus coordination.

This PR fixes the case where the reasoning parser branch in stream_chat_completion() skips tool call parsing entirely, causing <tool_call> markup to leak into content when both --reasoning-parser and --tool-call-parser are enabled. Mergeable on current main.

Coordination: at least two other PRs target the same problem area:

All three address the same root cause with slightly different approaches. They are also collectively the fix for issues #107 (Dubworx) and #161 (Thump604, "Reasoning content leaks into content field when tool calls are present").

Last activity Feb 16. This PR is the oldest of the three. Worth a coordination decision: which approach to land, then close or rebase the others.

@janhilgard
Copy link
Copy Markdown
Collaborator Author

This PR is now superseded by #253 ("integrate tool call parser into reasoning parser streaming path") which was merged into main. The fix covers the same issue — tool call parsing was missing in the reasoning parser streaming branch.

The current main has tool call parsing in both reasoning and non-reasoning streaming paths (4 call sites for extract_tool_calls_streaming).

This PR can be closed.

@janhilgard janhilgard closed this Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants